package validator

import (
	"encoding/json"
	"fmt"
	"github.com/wanchain/go-mpc/common"
	"github.com/wanchain/go-mpc/common/hexutil"
	"github.com/wanchain/go-mpc/crypto"
	"github.com/wanchain/go-mpc/log"
	"github.com/wanchain/go-mpc/storeman/btc"
	mpcprotocol "github.com/wanchain/go-mpc/storeman/storemanmpc/protocol"
	"math/big"
	"time"
)

////////////////////// add for Btc begin

func GetKeyFromBtcTx(args *btc.MsgTxArgs) (keyWithoutTxIn []byte, keyWithTxIn []byte) {
	keyWithoutTxIn = append(keyWithoutTxIn, big.NewInt(int64(args.Version)).Bytes()...)
	keyWithoutTxIn = append(keyWithoutTxIn, big.NewInt(int64(args.LockTime)).Bytes()...)

	for _, out := range args.TxOut {
		keyWithoutTxIn = append(keyWithoutTxIn, big.NewInt(int64(out.Value)).Bytes()...)
		keyWithoutTxIn = append(keyWithoutTxIn, []byte(out.PkScript)...)
		log.SyslogInfo("GetKeyFromBtcTx", "out.PkScript", out.PkScript)
		//break
	}

	keyWithTxIn = make([]byte, len(keyWithoutTxIn))
	copy(keyWithTxIn, keyWithoutTxIn)
	for _, in := range args.TxIn {
		log.SyslogInfo("GetKeyFromBtcTx", "txInPreOutHash", in.PreviousOutPoint.Hash, "txInIndex", in.PreviousOutPoint.Index)
		keyWithTxIn = append(keyWithTxIn, in.PreviousOutPoint.Hash[:]...)
		keyWithTxIn = append(keyWithTxIn, big.NewInt(int64(in.PreviousOutPoint.Index)).Bytes()...)
		keyWithTxIn = append(keyWithTxIn, []byte(in.PkScript)...)
		keyWithTxIn = append(keyWithTxIn, big.NewInt(int64(in.Sequence)).Bytes()...)
	}
	log.SyslogInfo("GetKeyFromBtcTx", "keyWithTxin", common.ToHex(keyWithTxIn), "keyWithoutTxIn", common.ToHex(keyWithoutTxIn))
	keyWithoutTxIn = crypto.Keccak256(keyWithoutTxIn)
	keyWithTxIn = crypto.Keccak256(keyWithTxIn)

	return keyWithoutTxIn, keyWithTxIn
}

func AddValidMpcBtcTx(args *btc.MsgTxArgs) error {
	log.SyslogInfo("AddValidMpcBtcTx, begin", "txInfo", args.String())

	msgTx, err := btc.GetMsgTxFromMsgTxArgs(args)
	if err != nil {
		return err
	}

	for _, txIn := range msgTx.TxIn {
		log.SyslogInfo("AddValidMpcBtcTx", "txInPreOutHash", txIn.PreviousOutPoint.Hash.String(), "txInIndex", txIn.PreviousOutPoint.Index)
	}
	for _, txOut := range msgTx.TxOut {
		log.SyslogInfo("AddValidMpcBtcTx", "txOutPkScript", common.ToHex(txOut.PkScript))
	}

	_, key := GetKeyFromBtcTx(args)
	keyApproved := buildKeyFromBytes(key, mpcprotocol.MpcApprovedBtc)
	val, err := json.Marshal(args)
	if err != nil {
		log.SyslogErr("AddValidMpcBtcTx, marshal fail", "err", err.Error())
		return err
	}
	log.SyslogInfo("AddValidMpcBtcTx before addKeyValueToDB", ">>>>>key", common.ToHex(key), ">>>>keyApproved", common.ToHex(keyApproved))
	//return addKeyValueToDB(key, val)
	return addKeyValueToDB(keyApproved, val)
}

func ValidateBtcTx(args *btc.MsgTxArgs) bool {
	if args == nil {
		return false
	}

	log.SyslogInfo("ValidateBtcTx, begin", "txInfo", args.String())

	/*
		//todo think it again,now comment the check
		var txOutScript [25]byte
		txOutScript[0] = txscript.OP_DUP
		txOutScript[1] = txscript.OP_HASH160
		txOutScript[2] = 0x14
		//copy(txOutScript[3:23], args.From[:])
		var addr common.Address
		addr = args.FromAddr()
		copy(txOutScript[3:23], addr[:])
		txOutScript[23] = txscript.OP_EQUALVERIFY
		txOutScript[24] = txscript.OP_CHECKSIG

		for i := 1; i < len(args.TxOut); i++ {
			log.SyslogInfo("ValidateBtcTx", "outScript", args.TxOut[i].PkScript)

			if !bytes.Equal(txOutScript[:], common.FromHex(args.TxOut[i].PkScript)) {
				log.SyslogErr("ValidateBtcTx, check has fail", "err", "invalid tx out pkscript")
				return false
			}
		}
	*/

	keyWithoutTxin, keyWithTxin := GetKeyFromBtcTx(args)
	keyWithoutTxinApproved := buildKeyFromBytes(keyWithoutTxin, mpcprotocol.MpcApprovedBtc)
	keyWithTxinApproved := buildKeyFromBytes(keyWithTxin, mpcprotocol.MpcApprovedBtc)

	log.SyslogInfo("ValidateBtcTx",
		"#####keyWithoutTxin#####", common.ToHex(keyWithoutTxin),
		"#####keyWithTxin#####", common.ToHex(keyWithTxin),
		"#####keyWithoutTxinApproved#####", common.ToHex(keyWithoutTxinApproved),
		"#####keyWithTxinApproved#####", common.ToHex(keyWithTxinApproved))

	//key, err := waitKeyFromDB([][]byte{keyWithTxin, keyWithoutTxin})
	key, err := waitKeyFromDB([][]byte{keyWithTxinApproved, keyWithoutTxinApproved})
	if err != nil {
		log.SyslogErr("ValidateBtcTx, check has fail", ">>>>>>key", common.ToHex(key), "err", err.Error(), "txInfo", args.String())
		return false
	} else {
		log.SyslogInfo("ValidateBtcTx successfully, key is got", ">>>>>>key", common.ToHex(key), "txInfo", args.String())
		return true
	}
}

func AddApprovingBtcTran(args *btc.MsgTxArgs) error {
	var msg btc.MsgTxArgs
	approvedKey := buildKeyFromBtc(args, mpcprotocol.MpcApprovedBtc)
	approvingKey := buildKeyFromBtc(args, mpcprotocol.MpcApprovingBtc)
	approvingKeysKey := []byte(mpcprotocol.MpcApprovingKeysBtc)

	args.RcvTimeStamp = time.Now()

	value, err := json.Marshal(&args)
	if err != nil {
		log.SyslogErr("AddApprovingBtcTran, json.Marshal fail", "err", err.Error())
		return err
	}
	json.Unmarshal(value, &msg)
	log.SyslogInfo("AddApprovingBtcTran",
		"@@@@@approvingKey@@@@@", hexutil.Encode(approvingKey),
		"@@@@@approvedKey@@@@@", hexutil.Encode(approvedKey),
		"value", (&msg).String())
	return addApproving(approvedKey, approvingKey, approvingKeysKey, value)
}

func GetBtcTransForApprove(duration time.Duration) ([]btc.MsgTxArgs, error) {
	log.SyslogInfo("GetBtcTransForApprove, begin")

	approvingBytes, _ := getForApprove([]byte(mpcprotocol.MpcApprovingKeysBtc))

	var approvingData []btc.MsgTxArgs
	for i := 0; i < len(approvingBytes); i++ {

		var approvingTemp btc.MsgTxArgs
		err := json.Unmarshal(approvingBytes[i], &approvingTemp)
		if err != nil {
			log.SyslogErr("GetDataForApprove, Unmarshal approvingTemp", "err", err.Error())
			//return nil, err
			continue
		}
		if approvingTemp.RcvTimeStamp.Add(duration).After(time.Now()) {
			approvingData = append(approvingData, approvingTemp)
		}
	}
	return approvingData, nil
}

func ApproveBtcTrans(args []*btc.MsgTxArgs) []error {
	retResult := make([]error, len(args))
	for i := 0; i < len(args); i++ {
		dataItem := args[i]
		log.SyslogInfo("ApproveBtcTrans", "txInfo", dataItem.String())
		retResult[i] = approveOneBtcTran(dataItem)
	}

	return retResult
}

func buildKeyBytesFromBtc(data *btc.MsgTxArgs) []byte {
	// build the key.
	_, keyWithIn := GetKeyFromBtcTx(data)
	return keyWithIn
}

// status: approving || approved
func buildKeyFromBtc(data *btc.MsgTxArgs, status string) []byte {
	// build the key.
	keyBytes := buildKeyBytesFromBtc(data)
	return buildKeyFromBytes(keyBytes, status)
}

func approveOneBtcTran(dataItem *btc.MsgTxArgs) error {
	approvedKey := buildKeyFromBtc(dataItem, mpcprotocol.MpcApprovedBtc)
	approvingKey := buildKeyFromBtc(dataItem, mpcprotocol.MpcApprovingBtc)
	approvingKeysKey := []byte(mpcprotocol.MpcApprovingKeysBtc)
	value, err := json.Marshal(&dataItem)
	if err != nil {
		log.SyslogErr("approveOneBtcTran, json.Marshal fail", "err", err.Error())
		return err
	}

	log.SyslogInfo("approveOneBtcTran",
		"transactionInfo", dataItem.String(),
		"approvedKey", hexutil.Encode(approvedKey),
		"approvingKey", hexutil.Encode(approvingKey))

	return approve(approvedKey, approvingKey, approvingKeysKey, value)
}

func GetMpcBtcTranStatus(args *btc.MsgTxArgs) (ret []string, err error) {
	keyWithoutTxin, keyWithTxin := GetKeyFromBtcTx(args)
	keyWithoutTxinApproved := buildKeyFromBytes(keyWithoutTxin, mpcprotocol.MpcApprovedBtc)
	keyWithTxinApproved := buildKeyFromBytes(keyWithTxin, mpcprotocol.MpcApprovedBtc)

	keyWithoutTxinApproving := buildKeyFromBytes(keyWithoutTxin, mpcprotocol.MpcApprovingKeysBtc)
	keyWithTxinApproving := buildKeyFromBytes(keyWithTxin, mpcprotocol.MpcApprovingKeysBtc)

	sdb, err := GetDB()
	if err != nil {
		log.SyslogErr("approve, getting storeman database fail", "err", err.Error())
		return nil, err
	}

	strTemp := ""

	exist, err := sdb.Has(keyWithoutTxinApproving)
	if !exist {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithoutTxinApproving", hexutil.Encode(keyWithoutTxinApproving), "false")
	} else {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithoutTxinApproving", hexutil.Encode(keyWithoutTxinApproving), "true")
	}
	ret = append(ret, strTemp)

	exist, err = sdb.Has(keyWithTxinApproving)
	if !exist {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithTxinApproving", hexutil.Encode(keyWithTxinApproving), "false")
	} else {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithTxinApproving", hexutil.Encode(keyWithTxinApproving), "true")
	}
	ret = append(ret, strTemp)

	exist, err = sdb.Has(keyWithoutTxinApproved)
	if !exist {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithoutTxinApproved", hexutil.Encode(keyWithoutTxinApproved), "false")
	} else {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithoutTxinApproved", hexutil.Encode(keyWithoutTxinApproved), "true")
	}
	ret = append(ret, strTemp)

	exist, err = sdb.Has(keyWithTxinApproved)
	if !exist {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithTxinApproved", hexutil.Encode(keyWithTxinApproved), "false")
	} else {
		strTemp = fmt.Sprintf("%s:%v:%s", "keyWithTxinApproved", hexutil.Encode(keyWithTxinApproved), "true")
	}
	ret = append(ret, strTemp)

	return ret, nil
}
